package com.mangix.scheduler;

import com.mangix.core.MangixNode;
import com.mangix.files.MangixFiles;
import com.mangix.utils.MangixUtils;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import static java.nio.file.FileVisitResult.*;

/**
 * Created by ZXL on 2014/6/10.
 */
public class MangixDiskChecker extends Thread {

    private MangixFiles mangixFiles;
    private MangixNode localNode;
    private int dirOffset;
    private int M;
    private Iterator<Path> folderIterator;
    private DirectoryStream<Path> folderStream;
    private Iterator<Path> objectIterator;
    private DirectoryStream<Path> objectStream;
    private  Thread[] transferPool;
    private ConcurrentHashMap<Path, Boolean> transferTaskMap;
    private BlockingQueue<Path> transferTaskQueue;

    private  Thread[] replicatorPool;
    private ConcurrentHashMap<Path, Boolean> replicationTaskMap;
    private BlockingQueue<Path> replicationTaskQueue;

    public MangixDiskChecker(MangixNode localNode) {
        this.localNode = localNode;
        this.mangixFiles = localNode.getFiles();
        this.M = localNode.getConfig().getM();
        this.dirOffset = -1;
        this.folderIterator = null;
        this.objectIterator = null;
        this.folderStream = null;
        this.objectStream = null;
        // Init MangixObjectTransmitter.
        this.transferTaskMap = new ConcurrentHashMap<>();
        this.transferTaskQueue = new LinkedBlockingDeque<>(50);
        this.transferPool = new Thread[1];
        for (int i = 0; i < this.transferPool.length; i++) {
            this.transferPool[i] = new MangixTransfer(transferTaskMap, transferTaskQueue, localNode);
            this.transferPool[i].start();
        }
        // Init MangixReplicator.
        this.replicationTaskMap = new ConcurrentHashMap<>();
        this.replicationTaskQueue = new LinkedBlockingDeque<>(50);
        this.replicatorPool = new Thread[1];
        for (int i = 0; i < this.replicatorPool.length; i++) {
            this.transferPool[i] = new MangixReplicator(replicationTaskMap, replicationTaskQueue, localNode);
            this.transferPool[i].start();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000*60);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    deleteEmptyFolder();
                }
            }
        }).start();
    }

    private void deleteEmptyFolder() {

    }

    public class DiskChecker extends SimpleFileVisitor<Path> {

        /**
         * Print information about each type of file.
         * @param file
         * @param attr
         * @return
         */
        @Override
        public FileVisitResult visitFile(Path file,
                                         BasicFileAttributes attr) {
            if (attr.isRegularFile()) {
                Path latestFile = mangixFiles.getLatestFile(file.getParent().getFileName().toString());
                if (latestFile != null) {
                    try {
                        if (isLocalObject(file.getParent().getFileName().toString(), latestFile)) {
                            synchronization(latestFile);
                        } else {
                            transfer(latestFile);
                        }
                    } catch (NoSuchFileException e) {
                        // Do nothing.
                    }  catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } else {
                // Remove any other files.
                try {
                    Files.delete(file);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return CONTINUE;
        }

        /**
         * Delete old files..
         */
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            Path[] dataDirs = mangixFiles.getDataDirs();
            boolean isKeyDir = false;
            Path parentDir = dir.getParent().getParent();
            for (Path dataDir: dataDirs) {
                if (dataDir.equals(parentDir)) {
                    isKeyDir = true;
                }
            }
            if (isKeyDir) {
                mangixFiles.deleteOldFiles(dir);
            }
            boolean isDataDir = false;
            for (Path dataDir: dataDirs) {
                if (dataDir.equals(dir)) {
                    isDataDir = true;
                }
            }
            if (!isDataDir) {
                mangixFiles.deleteEmptyFolder(dir);
            }
            return CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            Path[] dataDirs = mangixFiles.getDataDirs();
            boolean isDataDir = false;
            for (Path dataDir: dataDirs) {
                if (dataDir.equals(dir)) {
                    isDataDir = true;
                }
            }
            if (!isDataDir) {
                mangixFiles.deleteEmptyFolder(dir);
            }
            return CONTINUE;
        }

        // If there is some error accessing the file, let the user know.
        // If you don't override this method and an error occurs, an IOException is thrown.
        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            System.err.println(exc);
            return CONTINUE;
        }
    }

    public void run() {
        boolean isFirstTime = true;
        while (true) {
            if (isFirstTime) {
                long lastUpdateTime = localNode.getLastUpdateTime();
                long currentTime = System.currentTimeMillis();
                if (currentTime - lastUpdateTime < 1000 * 10) {
                    try {
                        Thread.sleep(1000 * 10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                isFirstTime = false;
             }
            try {
                // Walking the data directories tree, checker each object file.
                Path[] dataDirs = mangixFiles.getDataDirs();
                DiskChecker checker = new DiskChecker();
                for (Path dataDir : dataDirs) {
                    Files.walkFileTree(dataDir, checker);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(1000 * 60 * 60);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    private boolean isLocalObject(String key, Path obj) throws NoSuchFileException {
        try {
            int replicas = mangixFiles.getReplicasNum(obj);
            List<MangixNode> predecessorList = localNode.getPredecessorList();
            if (predecessorList.size() < replicas) {
                return true;
            } else {
                long id = MangixUtils.hash(key, M);
                MangixNode currentNode = localNode;
                for (int i = 0; i < replicas; i ++) {
                    MangixNode predecessor = predecessorList.get(i);
                    if (MangixUtils.isIDInInterval(id, predecessor.hash(), currentNode.hash(), false, true, M)) {
                        return true;
                    }
                    currentNode = predecessor;
                }
                return false;
            }
        } catch (NoSuchFileException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            return true;
        }
    }

    /**
     * Transmit this file to another node and delete it.
     */
    private void transfer(Path latestFile) {
        if (transferTaskMap.get(latestFile) == null)
            if (transferTaskQueue.offer(latestFile)) {
                transferTaskMap.putIfAbsent(latestFile, true);
            }
    }

    /**
     * Replicate this file to others.
     */
    private void synchronization(Path latestFile) {
        if (replicationTaskMap.get(latestFile) == null)
            if (replicationTaskQueue.offer(latestFile)) {
                replicationTaskMap.putIfAbsent(latestFile, true);
            }
    }

}
